package com.spring.sd;


import com.spring.sd.bpp.Bean1;
import com.spring.sd.bpp.Bean2;
import com.spring.sd.bpp.Bean3;
import com.spring.sd.bpp.Bean4;
import org.springframework.beans.PropertyValues;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.InjectionMetadata;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.boot.context.properties.ConfigurationPropertiesBindingPostProcessor;
import org.springframework.context.annotation.CommonAnnotationBeanPostProcessor;
import org.springframework.context.annotation.ContextAnnotationAutowireCandidateResolver;
import org.springframework.context.support.GenericApplicationContext;
import org.springframework.core.MethodParameter;
import org.springframework.core.env.StandardEnvironment;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class BeanPostProcessorApplication {

    public static void main(String[] args) throws Throwable {
        //step1();
        //step2();
        //step3();
        //step4();
        step5();
    }

    /**
     * 使用一个GenericApplicationContext 是一个干净的容器<br/>
     * 运行后，发现Bean1 中使用的注解并没有生效，属性都是null。
     *
     * 向 GenericApplicationContext 添加一些与 Bean 后置处理器相关的 Bean，使得 Bean1 中使用的注解能够生效。
     *
     */
    public static void step1() {
        // GenericApplicationContext 是一个干净的容器
        GenericApplicationContext context = new GenericApplicationContext();
        // 用原始方式注册三个 bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);

        // 初始化容器。执行 beanFactory 后置处理器，添加 bean 后置处理器，初始化所有单例
        context.refresh();

        // Bean1(bean2=null, bean3=null, home=null)
        Bean1 bean1 = context.getBean(Bean1.class);
        System.out.println(bean1);

        // 销毁容器
        context.close();
    }


    /**
     * 加入处理@Autowired @Value @Resource @PostConstruct @PreDestroy的BeanPostProcessor
     */
    public static void step2() {
        // GenericApplicationContext 是一个干净的容器
        GenericApplicationContext context = new GenericApplicationContext();
        // 用原始方式注册三个 bean
        context.registerBean("bean1", Bean1.class);
        context.registerBean("bean2", Bean2.class);
        context.registerBean("bean3", Bean3.class);

        // AutowiredAnnotationBeanPostProcessor 用于解析 @Autowired 和 @Value 注解
        context.registerBean(AutowiredAnnotationBeanPostProcessor.class);
        // 解析值注入内容，解析@Value注入的home=${JAVA_HOME}这个值
        context.getDefaultListableBeanFactory().setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

        // @Resource @PostConstruct @PreDestroy
        context.registerBean(CommonAnnotationBeanPostProcessor.class);

        // 初始化容器。执行 beanFactory 后置处理器，添加 bean 后置处理器，初始化所有单例
        context.refresh();

        // Bean1(bean2=null, bean3=null, home=null)
        Bean1 bean1 = context.getBean(Bean1.class);
        System.out.println(bean1);

        // 销毁容器
        context.close();
    }

    /**
     * 解析 @ConfigurationProperties
     */
    public static void step3() {
        // GenericApplicationContext 是一个干净的容器
        GenericApplicationContext context = new GenericApplicationContext();
        context.registerBean("bean4", Bean4.class);

        // @ConfigurationProperties
        ConfigurationPropertiesBindingPostProcessor.register(context.getDefaultListableBeanFactory());

        // 初始化容器。执行 beanFactory 后置处理器，添加 bean 后置处理器，初始化所有单例
        context.refresh();

        Bean4 bean4 = context.getBean(Bean4.class);
        System.out.println(bean4);

        // 销毁容器
        context.close();
    }

    /**
     * AutowiredAnnotationBeanPostProcessor用于解析 @Autowired 和 @Value 注解，那它究竟是怎么工作的呢？
     */
    public static void step4() {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        // 注册成品 Bean，不再进行 Bean 的创建、依赖注入、初始化等操作
        beanFactory.registerSingleton("bean2", new Bean2());
        beanFactory.registerSingleton("bean3", new Bean3());
        // @Value
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());

        // 查看哪些属性、方法加了 @Autowired，这称之为 InjectionMetadata
        AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();
        postProcessor.setBeanFactory(beanFactory);

        // ${} 的解析器，从环境变量中读取解析home=${JAVA_HOME}
        beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

        //CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor = new CommonAnnotationBeanPostProcessor();
        //commonAnnotationBeanPostProcessor.setBeanFactory(beanFactory);

        Bean1 bean1 = new Bean1();
        System.out.println(bean1);
        // 执行依赖注入，@Autowired、@Value
        postProcessor.postProcessProperties(null, bean1, "bean1");
        //commonAnnotationBeanPostProcessor.postProcessProperties(null, bean1, "bean1");

        /**
         * 在未调用 AutowiredAnnotationBeanPostProcessor#postProcessProperties() 方法时，
         * Bean1 中的 bean2、bean3 和 home 都没有注入成功，而在调用之后，成功注入了 bean2 和 home，
         * 但 home 的值似乎有点奇怪，并没有打印出前文中相同的值，可能是因为没有成功解析 #{}？
         *
         * 至于 bean3 为什么没注入成功，是因为 bean3 的注入是利用 @Resource，而不是 @Autowired。
         * 如果对 Bean1 进行修改：
         *     @Autowired
         *     private Bean3 bean3;
         *
         * 或者加入@Resource注解的解析BPP
         *
         * CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor = new CommonAnnotationBeanPostProcessor();
         * commonAnnotationBeanPostProcessor.setBeanFactory(beanFactory);
         *
         * commonAnnotationBeanPostProcessor.postProcessProperties(null, bean1, "bean1");
         */
        System.out.println(bean1);


    }


    /**
     * org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata.findAutowiringMetadata()
     * 其中的 findAutowiringMetadata() 用于查找指定的 bean 对象中哪些地方使用了 @Autowired、@Value 等与注入相关的注解，
     * 并将这些信息封装在 InjectionMetadata 对象中，之后调用其 inject() 方法利用反射完成注入。
     *
     * findAutowiringMetadata() 方法是一个私有方法，尝试利用反射进行调用并进行断点查看 InjectionMetadata 对象中的信息：
     *
     * InjectionMetadata 中有一个名为 injectedElements 的集合类型成员变量，根据上图所示，injectedElements 存储了被相关注解标记的成员变量、方法的信息，因为 Bean1 中的 bean3 成员变量、setBean2() 和 setHome() 方法恰好被 @Autowired 注解标记。
     *
     * 然后按照源码一样，调用 InjectionMetadata#inject() 方法进行依赖注入
     *
     *
     * @throws Exception
     */
    public static void step5() throws Throwable {
        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        beanFactory.addEmbeddedValueResolver(new StandardEnvironment()::resolvePlaceholders);

        AutowiredAnnotationBeanPostProcessor postProcessor = new AutowiredAnnotationBeanPostProcessor();
        postProcessor.setBeanFactory(beanFactory);
        beanFactory.registerSingleton("bean2", new Bean2());
        beanFactory.registerSingleton("bean3", new Bean3());

        Bean1 bean1 = new Bean1();
        Method method = AutowiredAnnotationBeanPostProcessor.class.getDeclaredMethod("findAutowiringMetadata", String.class, Class.class, PropertyValues.class);
        method.setAccessible(true);
        // 获取 Bean1 上加了 @Value、@Autowired 注解的成员变量、方法参数信息
        InjectionMetadata metadata = (InjectionMetadata) method.invoke(postProcessor, "bean1", Bean1.class, null);
        // 此处断点
        System.out.println(metadata);

        // 调用 InjectionMetadata 来进行依赖注入，注入时按类型查找值
        metadata.inject(bean1, "bean1", null);
        System.out.println(bean1);


        // 调用 inject() 方法后会利用反射进行依赖注入，但在反射之前，肯定得先拿到被注入的对象或值，那这些对象或值是怎么取到的呢？
        // 可以通过以下代码概括
        Field bean3 = Bean1.class.getDeclaredField("bean3"); // 获取声明的变量
        DependencyDescriptor dd1 = new DependencyDescriptor(bean3, false); // 依赖描述符
        Object o1 = beanFactory.doResolveDependency(dd1, null, null, null);
        System.out.println(o1);

        Method setBean2 = Bean1.class.getDeclaredMethod("setBean2", Bean2.class);
        // MethodParameter 构造方法的第二个参数表示需要解析的方法中参数的索引
        DependencyDescriptor dd2 = new DependencyDescriptor(new MethodParameter(setBean2, 0), false);
        Object o2 = beanFactory.doResolveDependency(dd2, null, null, null);
        System.out.println(o2);

        Method setHome = Bean1.class.getDeclaredMethod("setHome", String.class);
        DependencyDescriptor dd3 = new DependencyDescriptor(new MethodParameter(setHome, 0), true);
        Object o3 = beanFactory.doResolveDependency(dd3, null, null, null);
        System.out.println(o3);

    }
}
